#include "Generation.h"
#include "parameters.h"

#include "Chromosome.h"
#include "AbstractChromosomeFactory.h"
#include "SelectionStrategy.h"

Generation::Generation()
{
    TRACE_FUNC
}

Generation::Generation(int generationSize, AbstractChromosomeFactory* chromosomeFactory)
{
    TRACE_FUNC_ENTRY

    if(chromosomeFactory == NULL)
        qFatal("NULL factory, aborting");
    randomChromosomes(generationSize, chromosomeFactory);
    TRACE_FUNC_EXIT
}

Generation::Generation(const Generation& other)
{
    TRACE_FUNC_ENTRY

    for(int i=0;i<other.chromosomeList.size();++i)
    {
        chromosomeList.append(other.chromosomeList[i]->duplicate());
    }

    TRACE_FUNC_EXIT
}

Generation::~Generation()
{
    TRACE_FUNC_ENTRY
    for(int i=0;i<chromosomeList.size();++i)
    {
        //qDebug() << "i: " << i;
        if(chromosomeList[i] != NULL)
        {
            delete chromosomeList[i];
            chromosomeList[i] = NULL;
        }
    }
    TRACE_FUNC_EXIT
}

Generation& Generation::operator=(const Generation& other)
{
    TRACE_FUNC_ENTRY

    if (&other == this)
        return *this;

    for(int i=0;i<chromosomeList.size();++i)
    {
        if(chromosomeList[i] != NULL)
        {
            delete chromosomeList[i];
            chromosomeList[i] = NULL;
        }
    }

    chromosomeList.clear();

    for(int i=0;i<other.chromosomeList.size();++i)
    {
        chromosomeList.append(other.chromosomeList[i]->duplicate());
    }

    TRACE_FUNC_EXIT
    return *this;
}

void Generation::randomChromosomes(int generationSize, AbstractChromosomeFactory* chromosomeFactory)
{
    TRACE_FUNC_ENTRY
    for(int i=0;i<generationSize;++i)
    {
        chromosomeList.append(chromosomeFactory->getNewRandomChromosome());
    }
    TRACE_FUNC_EXIT
}

void Generation::addChromosome(Chromosome* toAdd)
{
    TRACE_FUNC_ENTRY
    chromosomeList.append(toAdd);
    TRACE_FUNC_EXIT
}

int Generation::size() const
{
    TRACE_FUNC
    return chromosomeList.size();
}

double Generation::totalFitness() const
{
    TRACE_FUNC_ENTRY
    double totFitness = 0.0;

    for(int i=0;i<size();++i)
        totFitness += chromosomeList.at(i)->getFitness();

    TRACE_FUNC_EXIT
    return totFitness;
}

double Generation::minFitness() const
{
    return chromosomeList.last()->getFitness();
}
double Generation::maxFitness() const
{
    return chromosomeList.first()->getFitness();
}

Chromosome* Generation::getProcreator(SelectionStrategy* selectionStrategy)
{
    TRACE_FUNC
    if(selectionStrategy == NULL)
        qFatal("NULL strategy, aborting");
    return selectionStrategy->selectOne(chromosomeList);
}

void Generation::performCrossOver(double crossoverProb)
{
    TRACE_FUNC_ENTRY
    for(int i=0;i<chromosomeList.size();++i)
    {
        if( static_cast<double>(qrand()) / static_cast<double>(RAND_MAX) <= crossoverProb)
        {
            int partnerIndex = qrand() % chromosomeList.size();
            //qDebug() << "crossover " << i << " with " << partnerIndex;
            chromosomeList[i]->crossOver(*chromosomeList[partnerIndex]);
        }
    }
    TRACE_FUNC_EXIT
}

void Generation::performMutation(double mutationProb)
{
    TRACE_FUNC_ENTRY
    for(int i=0;i<chromosomeList.size();++i)
    {
        for(int j=0;j<chromosomeList.at(i)->size();++j)
        {
            if(static_cast<double>(qrand()) / static_cast<double>(RAND_MAX) <= mutationProb)
                chromosomeList[i]->mutate(j);
        }
    }
    TRACE_FUNC_EXIT
}

void Generation::performAdditionalOperators(double additionalOperatorProb1, double additionalOperatorProb2)
{
    TRACE_FUNC_ENTRY
    for(int i=0;i<chromosomeList.size();++i)
    {
        if(additionalOperatorProb1 > 0)
        {
            for(int j=0;j<chromosomeList.at(i)->size();++j)
            {
                if( static_cast<double>(qrand()) / static_cast<double>(RAND_MAX) <= additionalOperatorProb1)
                {
                    chromosomeList[i]->additionalOperators(j);
                }
            }
        }
        if(additionalOperatorProb2 > 0)
        {
            if( static_cast<double>(qrand()) / static_cast<double>(RAND_MAX) <= additionalOperatorProb2)
            {
                int partnerIndex = qrand() % chromosomeList.size();
                qDebug() << "additional " << i << " with " << partnerIndex;
                chromosomeList[i]->additionalOperators(*chromosomeList[partnerIndex]);
            }
        }
    }
    TRACE_FUNC_EXIT
}

Chromosome* Generation::getBestRepresentative() const
{
    TRACE_FUNC
    //TODO only when sorted
    return chromosomeList.first();
}

void Generation::replaceChromosome(int index, Chromosome* toAdd)
{
    TRACE_FUNC_ENTRY
    delete chromosomeList[index];
    chromosomeList.removeAt(index);
    chromosomeList.append(toAdd);
    TRACE_FUNC_EXIT
}

QList<int> Generation::findDuplicates()
{
    QList<int> duplList;
    for(int i=0;i<chromosomeList.size();++i)
    {
        for(int j=0;j<chromosomeList.size();++j)
        {
            if(i != j)
            {
                if(*chromosomeList[i] == *chromosomeList[j])
                {
                    if(!duplList.contains(i) && !duplList.contains(j))
                        duplList.append(j);
                }
            }
        }
    }

   // for(int i=0;i<duplList.size();++i)
   //     qDebug() << "dupl: " << duplList.at(i);

    return duplList;
}

//TODO
Chromosome& Generation::operator[](int index)
{
    TRACE_FUNC
    return *chromosomeList[index];
}

void Generation::sort()
{
    TRACE_FUNC_ENTRY
    qSort(chromosomeList.begin(), chromosomeList.end(), Chromosome::greaterThan);
    TRACE_FUNC_EXIT
}

QDebug operator<<(QDebug dbg, const Generation &gen)
{
    TRACE_FUNC_ENTRY
    dbg.nospace() << ">>generation size: " << gen.size() <<
            ", total fitness: " << gen.totalFitness() << endl;
    for(int i=0;i<gen.size();++i)
        dbg.nospace() << i << ": \t" << *gen.chromosomeList.at(i) << endl;

    dbg.nospace() << "<<generation" << endl;
    TRACE_FUNC_EXIT
    return dbg.maybeSpace();
}
